Webpack 性能优化
分析问题
- 未优化前构建需要多少时间,优化后构建需要多少时间?
- 有没有分析是什么问题导致构建速率变慢?
优化方面
从两个方面考虑:
- 有哪些方式可以减少 Webpack 的打包时间
- 有哪些方式可以让 Webpack 打出来的包更小. 优化这一点为主。
优化 webpack 打包速度
生产模式:
- 关闭 sourcemap (选择)
- webpack 配置给
optimization: { minimize: true }- build 环境下一般会对输出的 js 文件再进一步所压缩处理(但是会丢失 sourcemap, 线上出问题无法及时定位)
- 压缩只用于生产阶段.
- 添加 externals 分离出一些大的包,比如 vue
- 去 console 提升性能 (webpack 插件)
开发模式:
- 按需打包
- 构建优化:减少编译体积 ContextReplacementPlugin、IgnorePlugin、babel-plugin-import、babel-plugin-transform-runtime。
- 将变动很少的模块划分出 webpack 的主 bundlejs
- babel-loader ts(x) 处理之后上层加一层 cache-loader 用来缓存, 缓存 cache-loader、hard-source-webpack-plugin、uglifyjsWebpackPlugin 开启缓存、babel-loader 开启缓存
- 预编译, 使用 dll 插件优化打包时间。 DllPlugin + DllReferencePlugin
- HappyPack 开启多进程编译,但是也并不一定支持所有 loader 都适合。thread-loader、parallel-webpack、thread-loader、uglifyjsWebpackPlugin 开启并行
- 开发环境去除所有跟 minimize, chunk 之类的配置
- UglifyJsPlugin 压缩很慢,如何提高速度? 缓存原理,压缩只重新压缩改变的,还有就是减少冗余的代码,
性能优化
- 减少编译体积 Tree-shaking、Scope Hositing。
- hash 缓存 webpack-md5-plugin
- 拆包 splitChunksPlugin、import()、require.ensure
优化 Loader
将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间
loader: "babel-loader?cacheDirectory=true";
cache-loader
在性能开销较大的 loader 前面使用这个 loader 能够缓存住上一次的结果
HappyPack
受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。
HappyPack 可以将 Loader 的同步执行转换为并行的
module.exports = {
module: {
loaders: [
{
test: /\.js$/,
include: [resolve("src")],
exclude: /node_modules/,
// id 后面的内容对应下面
loader: "happypack/loader?id=happybabel",
},
],
},
plugins: [
new HappyPack({
id: "happybabel",
loaders: ["babel-loader?cacheDirectory"],
// 开启 4 个线程
threads: 4,
}),
],
};
DllPlugin
DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。
接下来我们就来学习如何使用 DllPlugin
// 单独配置在一个文件中
// webpack.dll.conf.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: {
// 想统一打包的类库
vendor: ["react"],
},
output: {
path: path.join(__dirname, "dist"),
filename: "[name].dll.js",
library: "[name]-[hash]",
},
plugins: [
new webpack.DllPlugin({
// name 必须和 output.library 一致
name: "[name]-[hash]",
// 该属性需要与 DllReferencePlugin 中一致
context: __dirname,
path: path.join(__dirname, "dist", "[name]-manifest.json"),
}),
],
};
然后我们需要执行这个配置文件生成依赖文件,接下来我们需要使用 DllReferencePlugin 将依赖文件引入项目中
// webpack.conf.js
module.exports = {
// ...省略其他配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// manifest 就是之前打包出来的 json 文件
manifest: require("./dist/vendor-manifest.json"),
}),
],
};
代码压缩
在 Webpack4 中,我们就不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。
多页面提取公共资源
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
commons: {
chunks: "initial",
minChunks: 2, //最小重复的次数
minSize: 0, //最小提取字节数
},
vendor: {
test: /node_modules/,
chunks: "initial",
name: "vendor",
},
},
},
},
};
webpack 4.6.0+增加了对预取和预加载的支持。
动态导入:注释中的使用 webpackChunkName,可以单独给 bundle 命名,lodash.bundle.js 而不是 [id].bundle.js。
import(/* webpackChunkName: "lodash" */ "lodash");
预取(prefetch):将来可能需要一些导航资源
- 只要父
chunk加载完成,webpack就会添加prefetch
import(/* webpackPrefetch: true */ "LoginModal");
// 将<link rel="prefetch" href="login-modal-chunk.js">其附加在页面的开头
预加载(preload):当前导航期间可能需要资源
preloadchunk 会在父 chunk 加载时,以并行方式开始加载- 不正确地使用
webpackPreload会有损性能,
import(/* webpackPreload: true */ "ChartingLibrary");
// 在加载父 chunk 的同时
// 还会通过 <link rel="preload"> 请求 charting-library-chunk
HappyPack
HappyPack可以开启多进程 Loader 转换,将任务分解给多个子进程,最后将结果发给主进程。
使用
exports.plugins = [
new HappyPack({
id: "jsx",
threads: 4,
loaders: ["babel-loader"],
}),
new HappyPack({
id: "styles",
threads: 2,
loaders: ["style-loader", "css-loader", "less-loader"],
}),
];
exports.module.rules = [
{
test: /\.js$/,
use: "happypack/loader?id=jsx",
},
{
test: /\.less$/,
use: "happypack/loader?id=styles",
},
];
ParallelUglifyPlugin
ParallelUglifyPlugin可以开启多进程压缩 JS 文件
import ParallelUglifyPlugin from "webpack-parallel-uglify-plugin";
module.exports = {
plugins: [
new ParallelUglifyPlugin({
test,
include,
exclude,
cacheDir,
workerCount,
sourceMap,
uglifyJS: {},
uglifyES: {},
}),
],
};